package com.googlecode.gaal.vis;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import com.googlecode.gaal.data.api.IntSequence;
import com.googlecode.gaal.data.api.SymbolTable;
import com.googlecode.gaal.suffix.api.EmbeddedSuffixTree.EmbeddedInterval;
import com.googlecode.gaal.suffix.api.EnhancedSuffixArray;
import com.googlecode.gaal.suffix.api.IntervalTree;
import com.googlecode.gaal.suffix.api.IntervalTree.Interval;
import com.googlecode.gaal.suffix.api.LinearizedSuffixTree;
import com.googlecode.gaal.suffix.api.LinearizedSuffixTree.BinaryInterval;
import com.googlecode.gaal.suffix.api.SuffixArray;
import com.googlecode.gaal.suffix.api.SuffixTree.Node;
import com.googlecode.gaal.vis.api.Drawing;
import com.googlecode.gaal.vis.api.NodeStyle;
import com.googlecode.gaal.vis.impl.TikzConstants;

public class TableVisualizer<S> {

    private final IntSequence sequence;
    private final SymbolTable<S> symbolTable;
    private Drawing drawing;
    private Map<Interval, Integer> depthNodes;
    private int[] usedCells;
    private int maxDepth;

    public TableVisualizer(IntSequence sequence, SymbolTable<S> symbolTable) {
        this.sequence = sequence;
        this.symbolTable = symbolTable;
    }

    public void visualizeSequence(Drawing drawing) {
        visualizeSubsequence(drawing, -1, -1);
    }

    public void visualizeSubsequence(Drawing drawing, int... indices) {
        int index = 0;
        for (int i = 0; i < sequence.size(); i++) {
            if (indices[index] == i) {
                drawing.drawCell(i, 0, symbolTable.toToken(sequence.get(i)).toString(), Integer.toString(i),
                        TikzConstants.BLUE_CELL);
                if (index < indices.length - 1) {
                    index++;
                }
            } else
                drawing.drawCell(i, 0, symbolTable.toToken(sequence.get(i)).toString(), Integer.toString(i),
                        TikzConstants.LIGHT_BLUE_CELL);
        }
    }

    public void visualizeComplexSubsequence(Drawing drawing, int... indices) {
        int index = 0;
        for (int i = 0; i < sequence.size(); i++) {
            if (indices[index] == i) {
                drawing.drawCell(i, 0, symbolTable.toToken(sequence.get(i)).toString(), Integer.toString(i),
                        TikzConstants.CELL_STYLES[indices[index + 1]]);
                if (index < indices.length - 2) {
                    index += 2;
                }
            } else
                drawing.drawCell(i, 0, symbolTable.toToken(sequence.get(i)).toString(), Integer.toString(i),
                        TikzConstants.LIGHT_BLUE_CELL);
        }
    }

    public void visualizeSuffixes(Drawing drawing) {
        for (int i = 0; i < sequence.size(); i++) {
            IntSequence subSequence = sequence.subSequence(i, sequence.size());
            drawing.drawCell(0, i, Integer.toString(i), TikzConstants.RED_CELL);

            for (int j = 0; j < subSequence.size(); j++) {
                drawing.drawCell(j + 1, i, symbolTable.toToken(subSequence.get(j)).toString(), Integer.toString(i + j),
                        TikzConstants.LIGHT_BLUE_CELL);
            }
        }
    }

    public void visualizeEmbeddedSuffixes(Drawing drawing, SuffixArray sa, Interval interval, int windowSize) {
        IntSequence indices = interval.indices();
        int lcp = interval.lcp();
        for (int i = 0; i < interval.size(); i++) {
            int start = indices.get(i);
            drawing.drawCell(0, i, Integer.toString(start), TikzConstants.RED_CELL);
            IntSequence subSequence = sequence.subSequence(start, sequence.size());
            for (int k = 0; k < subSequence.size(); k++) {
                if (k < lcp) {
                    drawing.drawCell(k + 1, i, symbolTable.toToken(subSequence.get(k)).toString(),
                            Integer.toString(start + k), TikzConstants.LIGHT_BLUE_CELL);
                } else {
                    drawing.drawCell(k + 1, i, symbolTable.toToken(subSequence.get(k)).toString(),
                            Integer.toString(start + k), TikzConstants.BLUE_CELL);
                }
            }
        }
    }

    public void visualizeWindow(Drawing drawing, SuffixArray sa, Interval interval, int windowSize) {
        IntSequence indices = interval.indices();
        int lcp = interval.lcp();
        for (int i = 0; i < interval.size(); i++) {
            int start = indices.get(i) + lcp;
            drawing.drawCell(0, i, Integer.toString(start), TikzConstants.RED_CELL);
            IntSequence subSequence = sequence.subSequence(start, sequence.size());
            for (int k = 0; k < subSequence.size(); k++) {
                if (k < windowSize) {
                    drawing.drawCell(k + 1, i, symbolTable.toToken(subSequence.get(k)).toString(),
                            Integer.toString(start + k), TikzConstants.GREEN_CELL);
                } else {
                    drawing.drawCell(k + 1, i, symbolTable.toToken(subSequence.get(k)).toString(),
                            Integer.toString(start + k), TikzConstants.LIGHT_BLUE_CELL);
                }
            }
        }
    }

    public void visualizeEmbeddedSuffixesInWindow(Drawing drawing, SuffixArray sa, Interval interval, int windowSize) {
        IntSequence indices = interval.indices();
        int lcp = interval.lcp();
        int[] inverseSuffixTable = sa.getInverseSuffixTable();
        SortedMap<Integer, Integer> embeddedSuffixTableIndices = new TreeMap<Integer, Integer>();
        int counter = 0;
        for (int i = 0; i < interval.size(); i++) {
            int start = indices.get(i) + lcp;
            for (int j = start; j < start + windowSize && j < sequence.size(); j++) {
                drawing.drawCell(0, counter, Integer.toString(j), TikzConstants.RED_CELL);
                drawing.drawCell(1, counter, Integer.toString(inverseSuffixTable[j]), TikzConstants.GREEN_CELL);
                IntSequence subSequence = sequence.subSequence(j, sequence.size());
                Integer startIndex = embeddedSuffixTableIndices.get(inverseSuffixTable[j]);
                if (startIndex == null || startIndex < start) {
                    embeddedSuffixTableIndices.put(inverseSuffixTable[j], start);
                }
                for (int k = 0; k < subSequence.size(); k++) {
                    drawing.drawCell(k + 2, counter, symbolTable.toToken(subSequence.get(k)).toString(),
                            Integer.toString(j + k), TikzConstants.LIGHT_BLUE_CELL);
                }
                counter++;
            }
        }
    }

    public void visualizeEmbeddedSuffixTable(Drawing drawing, SuffixArray sa) {
        int[] inverseSuffixTable = sa.getInverseSuffixTable();
        int[] suffixTable = sa.getSuffixTable();
        for (int i = 0; i < suffixTable.length; i++) {
            IntSequence subSequence = sequence.subSequence(suffixTable[i], sequence.size());
            drawing.drawCell(0, i, String.format("%d", suffixTable[i]), TikzConstants.RED_CELL);
            drawing.drawCell(1, i, Integer.toString(inverseSuffixTable[suffixTable[i]]), TikzConstants.GREEN_CELL);
            for (int j = 0; j < subSequence.size(); j++) {
                drawing.drawCell(j + 2, i, symbolTable.toToken(subSequence.get(j)).toString(),
                        Integer.toString(suffixTable[i] + j), TikzConstants.LIGHT_BLUE_CELL);
            }
        }
    }

    public void visualizeLcpInSuffixTable(Drawing drawing, SuffixArray sa) {
        int[] suffixTable = sa.getSuffixTable();
        int[] lcpTable = sa.getLcpTable();
        for (int i = 0; i < suffixTable.length; i++) {
            IntSequence subSequence = sequence.subSequence(suffixTable[i], sequence.size());
            int lcp = lcpTable[i];
            drawing.drawCell(0, i, Integer.toString(lcp), TikzConstants.GREEN_CELL);

            NodeStyle style = null;
            if (lcp > 0)
                style = TikzConstants.CELL_STYLES[lcp];
            for (int j = 0; j < subSequence.size(); j++) {
                NodeStyle cellStyle;
                if (j < lcp) {
                    cellStyle = style;
                } else {
                    cellStyle = TikzConstants.LIGHT_BLUE_CELL;
                }
                drawing.drawCell(j + 1, i, symbolTable.toToken(subSequence.get(j)).toString(),
                        Integer.toString(suffixTable[i] + j), cellStyle);
            }
        }
    }

    public void visualizeIndexTable(Drawing drawing, int length, String label, int labelLength, NodeStyle style) {
        for (int i = 0; i < length; i++) {
            drawing.drawCell(i, 0, Integer.toString(i), style);
        }
        if (labelLength != 0)
            drawing.drawBox(label, labelLength);
        else
            drawing.drawBox(label);
    }

    public void visualizeTable(Drawing drawing, int[] table, String label, int labelLength, NodeStyle style) {
        for (int i = 0; i < table.length; i++) {
            drawing.drawCell(i, 0, Integer.toString(table[i]), style);
        }
        if (labelLength != 0)
            drawing.drawBox(label, labelLength);
        else
            drawing.drawBox(label);
    }

    public void visualizeTable(Drawing drawing, String[] table, String label, Integer labelLength, NodeStyle style) {
        for (int i = 0; i < table.length; i++) {
            drawing.drawCell(i, 0, table[i], style);
        }
        if (labelLength != 0)
            drawing.drawBox(label, labelLength);
        else
            drawing.drawBox(label);
    }

    public void visualizeSuffixTable(Drawing drawing, SuffixArray sa) {
        int[] suffixTable = sa.getSuffixTable();
        for (int i = 0; i < suffixTable.length; i++) {
            IntSequence subSequence = sequence.subSequence(suffixTable[i], sequence.size());
            drawing.drawCell(0, i, String.format("%d", suffixTable[i]), TikzConstants.RED_CELL);

            for (int j = 0; j < subSequence.size(); j++) {
                drawing.drawCell(j + 1, i, symbolTable.toToken(subSequence.get(j)).toString(),
                        Integer.toString(suffixTable[i] + j), TikzConstants.LIGHT_BLUE_CELL);
            }
        }
    }

    public void visualizeInterval(Drawing drawing, Interval interval, int length, int windowSize) {
        int[] styleArray = new int[sequence.size()];
        Arrays.fill(styleArray, -1);
        markInterval(interval, null, styleArray, length, windowSize);
        NodeStyle style = null;
        for (int i = 0; i < styleArray.length; i++) {
            if (styleArray[i] != -1) {
                style = TikzConstants.CELL_STYLES[styleArray[i]];
            } else {
                style = TikzConstants.LIGHT_BLUE_CELL;
            }
            drawing.drawCell(i, 0, symbolTable.toToken(sequence.get(i)).toString(), Integer.toString(i), style);
        }
    }

    private void markInterval(Interval interval, EmbeddedInterval embeddedInterval, int[] styleArray, int length,
            int windowSize) {
        IntSequence indices = interval.indices();
        int lcp = interval.lcp();
        if (length < 1) {
            length = lcp;
        }
        for (int i = 0; i < indices.size(); i++) {
            int style = i;
            if (embeddedInterval != null) {
                IntSequence embeddedIndices = embeddedInterval.indices();
                int minDistance = windowSize + 1;
                for (int k = 0; k < embeddedIndices.size(); k++) {
                    int distance = embeddedIndices.get(k) - indices.get(i) - lcp;
                    if (distance >= 0 && distance < minDistance) {
                        minDistance = distance;
                    }
                }
                if (minDistance > windowSize) {
                    continue;
                }
                style = styleArray[indices.get(i) + lcp + minDistance];
            }
            for (int j = 0; j < length; j++) {
                styleArray[indices.get(i) + j] = style;
            }
        }
        if (interval instanceof EmbeddedInterval) {
            embeddedInterval = (EmbeddedInterval) interval;
            markInterval(embeddedInterval.getEmbeddingInterval(), embeddedInterval, styleArray, -1, windowSize);
        }
    }

    public <E extends Interval, T extends SuffixArray & IntervalTree<E>> int[] visualizeChildTable(Drawing drawing,
            T tree, Map<Interval, Integer> depthNodes, int[] totalUsedCells, int maxDepth) {
        if (depthNodes.size() != 0) {
            this.drawing = drawing;
            this.depthNodes = depthNodes;
            this.maxDepth = maxDepth;
            this.usedCells = new int[sequence.size()];
            // System.out.println(depthNodes);
            if (tree instanceof EnhancedSuffixArray) {
                visualizeChild((EnhancedSuffixArray) tree, ((EnhancedSuffixArray) tree).top(), 1);
            } else if (tree instanceof LinearizedSuffixTree) {
                visualizeChild((LinearizedSuffixTree) tree, ((LinearizedSuffixTree) tree).top(), 1);
            }
            for (int i = 0; i < usedCells.length; i++) {
                if (usedCells[i] == 0)
                    drawing.drawCell(i, 0, "", TikzConstants.LIGHT_BLUE_CELL);
                else
                    totalUsedCells[i] = usedCells[i];
            }
        } else {
            int[] childTable = null;
            if (tree instanceof EnhancedSuffixArray) {
                childTable = ((EnhancedSuffixArray) tree).getChildTable();
            } else if (tree instanceof LinearizedSuffixTree) {
                childTable = ((LinearizedSuffixTree) tree).getChildTable();
            }
            for (int i = 0; i < childTable.length; i++) {
                if (i == childTable.length - 1) {
                    drawing.drawCell(i, 0, "", TikzConstants.LIGHT_BLUE_CELL);
                } else {
                    if (totalUsedCells[i] == 1) {
                        drawing.drawCell(i, 0, Integer.toString(childTable[i]), TikzConstants.RED_CELL);
                    } else {
                        drawing.drawCell(i, 0, Integer.toString(childTable[i]), TikzConstants.GREEN_CELL);
                    }
                }
            }
        }
        return totalUsedCells;
    }

    private void visualizeChild(EnhancedSuffixArray esa, Node node, int depth) {
        if (maxDepth == 0) {
            visualizeCell(esa, esa.top(), true, false);
        } else {
            Iterator<Node> iterator = node.childIterator();
            boolean isFirst = true;
            while (iterator.hasNext()) {
                node = iterator.next();
                if (depth == maxDepth) {
                    // System.err.println("depth=" + depth + " " + node + " "
                    // + isFirst + " " + !iterator.hasNext());
                    if (iterator.hasNext()) {
                        visualizeCell(esa, node, false, isFirst);
                    } else {
                        visualizeCell(esa, node, true, isFirst);
                    }
                } else if (depth < maxDepth) {
                    visualizeChild(esa, node, depth + 1);
                }
                if (isFirst)
                    isFirst = false;
            }
        }
    }

    private void visualizeCell(EnhancedSuffixArray esa, Interval node, boolean isLast, boolean isFirst) {
        int child = -1;
        int next = -1;
        if (!node.isTerminal()) {
            if (isLast) {
                child = node.left();
            } else if (isFirst) {
                child = node.right();
            } else {
                child = node.right();
            }
        }
        if (!isLast && !isFirst) {
            next = node.left();
        }
        if (next != -1) {
            int number = drawing.drawCell(next, 0, Integer.toString(esa.getChildTable()[next]), TikzConstants.RED_CELL);
            usedCells[next] = 1;
            String anchor = "south";
            if (node.right() > next)
                anchor = "west";
            // System.out.println(node);
            drawing.drawEdge(number, "north", depthNodes.get(node), anchor, TikzConstants.RED_DOTTED_EDGE);
        }
        if (child != -1) {
            int number = drawing.drawCell(child, 0, Integer.toString(esa.getChildTable()[child]),
                    TikzConstants.GREEN_CELL);
            usedCells[child] = 2;
            String anchor = "south";
            if (node.left() < child)
                anchor = "east";
            else if (node.right() > child)
                anchor = "west";
            drawing.drawEdge(number, "north", depthNodes.get(node), anchor, TikzConstants.GREEN_DOTTED_EDGE);
        }
    }

    private void visualizeChild(LinearizedSuffixTree lst, BinaryInterval node, int depth) {
        if (maxDepth == 0) {
            visualizeCell(lst, lst.top(), false);
        } else {
            if (!node.isTerminal()) {
                BinaryInterval left = node.leftChild();
                BinaryInterval right = node.rightChild();
                if (depth == maxDepth) {
                    visualizeCell(lst, left, true);
                    visualizeCell(lst, right, false);
                } else if (depth < maxDepth) {
                    visualizeChild(lst, left, depth + 1);
                    visualizeChild(lst, right, depth + 1);
                }
            }
        }
    }

    private void visualizeCell(LinearizedSuffixTree lst, BinaryInterval node, boolean isFirst) {
        int child;
        if (isFirst) {
            child = node.right();
        } else {
            child = node.left();
        }
        if (!node.isTerminal()) {
            int number = drawing.drawCell(child, 0, Integer.toString(lst.getChildTable()[child]),
                    TikzConstants.GREEN_CELL);
            usedCells[child] = 2;
            String anchor = "south";
            if (node.left() < child)
                anchor = "east";
            else if (node.right() > child)
                anchor = "west";
            drawing.drawEdge(number, "north", depthNodes.get(node), anchor, TikzConstants.GREEN_DOTTED_EDGE);
        }
    }

    public <E extends Interval, T extends SuffixArray & IntervalTree<E>> void visualizeSuffixIntervals(Drawing drawing,
            T sa, Map<Interval, Integer> styleMap) {
        int[] suffixTable = sa.getSuffixTable();
        Iterator<? extends Interval> iterator = sa.preorderIterator();
        boolean[][] usedCells = new boolean[suffixTable.length][sequence.size()];
        while (iterator.hasNext()) {
            Interval node = iterator.next();
            Integer style = styleMap.get(node);
            if (style != null) {
                NodeStyle nodeStyle = TikzConstants.CELL_STYLES[style];
                for (int i = node.left(); i <= node.right(); i++) {
                    IntSequence subSequence = sequence.subSequence(suffixTable[i], suffixTable[i] + node.lcp());
                    for (int j = 0; j < subSequence.size(); j++) {
                        if (!usedCells[i][j]) {
                            usedCells[i][j] = true;
                            drawing.drawCell(i, j, symbolTable.toToken(subSequence.get(j)).toString(), nodeStyle);
                        }
                    }
                }
            }
        }

        for (int i = 0; i < suffixTable.length; i++) {
            IntSequence subSequence = sequence.subSequence(suffixTable[i], sequence.size());

            for (int j = 0; j < subSequence.size(); j++) {
                if (!usedCells[i][j]) {
                    drawing.drawCell(i, j, symbolTable.toToken(subSequence.get(j)).toString(),
                            TikzConstants.LIGHT_BLUE_CELL);
                }
            }
        }
    }
}
